/*************************************************************************/
/*  allocators.h                                                         */
/*************************************************************************/
/*                       This file is part of:                           */
/*                           GODOT ENGINE                                */
/*                    http://www.godotengine.org                         */
/*************************************************************************/
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur.                 */
/*                                                                       */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the       */
/* "Software"), to deal in the Software without restriction, including   */
/* without limitation the rights to use, copy, modify, merge, publish,   */
/* distribute, sublicense, and/or sell copies of the Software, and to    */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions:                                             */
/*                                                                       */
/* The above copyright notice and this permission notice shall be        */
/* included in all copies or substantial portions of the Software.       */
/*                                                                       */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
/*************************************************************************/
#ifndef ALLOCATORS_H
#define ALLOCATORS_H

#include "os/memory.h"
template<int PREALLOC_COUNT=64, int MAX_HANDS=8>
class BalloonAllocator {

	enum {

		USED_FLAG=(1<<30),
		USED_MASK=USED_FLAG-1
	};

	struct Balloon {

		Balloon *next;
		Balloon *prev;
		uint32_t hand;
	};


	struct Hand {

		int used;
		int allocated;
		Balloon *first;
		Balloon *last;
	};


	Hand hands[MAX_HANDS];



public:

	void* alloc(size_t p_size) {

		size_t max=(1<<MAX_HANDS);
		ERR_FAIL_COND_V( p_size>max, NULL );

		unsigned int hand=0;

		while(p_size>(size_t)(1<<hand)) ++hand;

		Hand &h=hands[hand];

		if (h.used==h.allocated) {

			for(int i=0;i<PREALLOC_COUNT;i++) {

				Balloon *b = (Balloon*)memalloc(sizeof(Balloon)+(1<<hand));
				b->hand=hand;
				if (h.last) {

					b->prev=h.last;
					h.last->next=b;
					h.last=b;
				} else {

					b->prev=NULL;
					h.last=b;
					h.first=b;
				}
			}

			h.last->next=NULL;
			h.allocated+=PREALLOC_COUNT;
		}

		Balloon *pick=h.last;

		ERR_FAIL_COND_V( (pick->hand&USED_FLAG), NULL );

		// remove last
		h.last=h.last->prev;
		h.last->next=NULL;

		pick->next=h.first;
		h.first->prev=pick;
		pick->prev=NULL;
		h.first=pick;
		h.used++;
		pick->hand|=USED_FLAG;

		return (void*)(pick+1);
	}

	void free(void* p_ptr) {

		Balloon *b=(Balloon*)p_ptr;
		b-=1;

		ERR_FAIL_COND(!(b->hand&USED_FLAG) );

		b->hand=b->hand&USED_MASK; // not used
		int hand=b->hand;

		Hand &h=hands[hand];

		if (b==h.first)
			h.first=b->next;

		if (b->prev)
			b->prev->next=b->next;
		if (b->next)
			b->next->prev=b->prev;

		if (h.last!=b) {
			h.last->next=b;
			b->prev=h.last;
			b->next=NULL;
			h.last=b;
		}

		h.used--;

		if (h.used<=(h.allocated-(PREALLOC_COUNT*2))) { // this is done to ensure no alloc/free is done constantly

			for(int i=0;i<PREALLOC_COUNT;i++) {
				ERR_CONTINUE( h.last->hand& USED_FLAG );

				Balloon *new_last=h.last->prev;
				if (new_last)
					new_last->next=NULL;
				memfree( h.last );
				h.last=new_last;
			}
			h.allocated-=PREALLOC_COUNT;
		}
	}

	BalloonAllocator() {

		for(int i=0;i<MAX_HANDS;i++) {

			hands[i].allocated=0;
			hands[i].used=0;
			hands[i].first=NULL;
			hands[i].last=NULL;
		}

	}

	void clear() {

		for(int i=0;i<MAX_HANDS;i++) {

			while(hands[i].first) {

				Balloon *b=hands[i].first;
				hands[i].first=b->next;
				memfree(b);
			}

			hands[i].allocated=0;
			hands[i].used=0;
			hands[i].first=NULL;
			hands[i].last=NULL;
		}
	}

	~BalloonAllocator() {

		clear();
	}
};


#endif // ALLOCATORS_H
